// SPDX-FileCopyrightText: 2017-2023 Giovanni Dante Grazioli <deroad@libero.it>
// SPDX-License-Identifier: BSD-3-Clause

import Long from './long.js';

var json_parse = function(options) {
	"use strict";
	// This is a function that can parse a JSON text, producing a JavaScript
	// data structure. It is a simple, recursive descent parser. It does not use
	// eval or regular expressions, so it can be used as a model for implementing
	// a JSON parser in other languages.
	// We are defining the function inside of another function to avoid creating
	// global variables.
	var at, // The index of the current character
		ch, // The current character
		escapee = {
			'"': '"',
			'\\': '\\',
			'/': '/',
			b: '\b',
			f: '\f',
			n: '\n',
			r: '\r',
			t: '\t'
		},
		text,
		error = function(m) {
			// Call error when something is wrong.
			throw {
				name: 'SyntaxError',
				message: m,
				at: at,
				text: text
			};
		},
		next = function(c) {
			// If a c parameter is provided, verify that it matches the current character.
			if (c && c !== ch) {
				error("Expected '" + c + "' instead of '" + ch + "'");
			}
			// Get the next character. When there are no more characters,
			// return the empty string.
			ch = text.charAt(at);
			at += 1;
			return ch;
		},
		number = function() {
			// Parse a number value.
			var number,
				string = '';
			if (ch === '-') {
				string = '-';
				next('-');
			}
			while (ch >= '0' && ch <= '9') {
				string += ch;
				next();
			}
			if (ch === '.') {
				string += '.';
				while (next() && ch >= '0' && ch <= '9') {
					string += ch;
				}
			}
			if (ch === 'e' || ch === 'E') {
				string += ch;
				next();
				if (ch === '-' || ch === '+') {
					string += ch;
					next();
				}
				while (ch >= '0' && ch <= '9') {
					string += ch;
					next();
				}
			}
			number = +string;
			if (!isFinite(number)) {
				error("Bad number");
			} else {
				if (string.indexOf('.') < 0) {
					try {
						return Long.from(string, true);
					} catch (e) {
						console.log(e.message);
						console.log(e.stack);
						return string;
					}
				}
				return number;
			}
		},
		string = function() {
			// Parse a string value.
			var hex,
				i,
				string = '',
				uffff;
			// When parsing for string values, we must look for " and \ characters.
			if (ch === '"') {
				while (next()) {
					if (ch === '"') {
						next();
						return string;
					}
					if (ch === '\\') {
						next();
						if (ch === 'u') {
							uffff = 0;
							for (i = 0; i < 4; i += 1) {
								hex = parseInt(next(), 16);
								if (!isFinite(hex)) {
									break;
								}
								uffff = uffff * 16 + hex;
							}
							string += String.fromCharCode(uffff);
						} else if (typeof escapee[ch] === 'string') {
							string += escapee[ch];
						} else {
							break;
						}
					} else {
						string += ch;
					}
				}
			}
			error("Bad string");
		},
		white = function() {
			// Skip whitespace.
			while (ch && ch <= ' ') {
				next();
			}
		},
		word = function() {
			// true, false, or null.
			switch (ch) {
				case 't':
					next('t');
					next('r');
					next('u');
					next('e');
					return true;
				case 'f':
					next('f');
					next('a');
					next('l');
					next('s');
					next('e');
					return false;
				case 'n':
					next('n');
					next('u');
					next('l');
					next('l');
					return null;
			}
			error("Unexpected '" + ch + "'");
		},
		value, // Place holder for the value function.
		array = function() {
			// Parse an array value.
			var array = [];
			if (ch === '[') {
				next('[');
				white();
				if (ch === ']') {
					next(']');
					return array; // empty array
				}
				while (ch) {
					array.push(value());
					white();
					if (ch === ']') {
						next(']');
						return array;
					}
					next(',');
					white();
				}
			}
			error("Bad array");
		},
		object = function() {
			// Parse an object value.
			var key,
				object = {};
			if (ch === '{') {
				next('{');
				white();
				if (ch === '}') {
					next('}');
					return object; // empty object
				}
				while (ch) {
					key = string();
					white();
					next(':');
					object[key] = value();
					white();
					if (ch === '}') {
						next('}');
						return object;
					}
					next(',');
					white();
				}
			}
			error("Bad object");
		};
	value = function() {
		// Parse a JSON value. It could be an object, an array, a string, a number,
		// or a word.
		white();
		switch (ch) {
			case '{':
				return object();
			case '[':
				return array();
			case '"':
				return string();
			case '-':
				return number();
			default:
				return ch >= '0' && ch <= '9' ? number() : word();
		}
	};
	// Return the json_parse function. It will have access to all of the above
	// functions and variables.
	return function(source, reviver) {
		var result;
		text = source + '';
		at = 0;
		ch = ' ';
		result = value();
		white();
		if (ch) {
			error("Syntax error");
		}
		// If there is a reviver function, we recursively walk the new structure,
		// passing each name/value pair to the reviver function for possible
		// transformation, starting with a temporary root object that holds the result
		// in an empty key. If there is not a reviver function, we simply return the
		// result.
		return typeof reviver === 'function' ? (function walk(holder, key) {
			var v, value = holder[key];
			if (value && typeof value === 'object') {
				Object.keys(value).forEach(function(k) {
					v = walk(value, k);
					if (v !== undefined) {
						value[k] = v;
					} else {
						delete value[k];
					}
				});
			}
			return reviver.call(holder, key, value);
		}({
			'': result
		}, '')) : result;
	};
};

function _iterate_value(variable, pad, depth, used) {
	if (used.indexOf(variable) >= 0) {
		return null;
	}
	var spad = pad.repeat(depth);
	var nl = pad.length > 0 ? "\n" : "";
	var sp = "\"";
	var k, sval, str = "";
	var comma = false;
	if (Array.isArray(variable)) {
		used.push(variable);
		if (variable.length < 1) {
			return "[]";
		}
		str = "[" + nl;
		for (k = 0; k < variable.length; k++) {
			sval = _iterate_value(variable[k], pad, depth + 1, used);
			if (sval) {
				comma = true;
				str += spad + sval + "," + nl;
			}
		}
		if (comma) {
			str = str.slice(0, 0 - ("," + nl).length) + nl;
			str += pad.repeat(depth - 1) + "]";
		} else {
			str = str.slice(0, -1) + "]";
		}
		return str;
	} else if (["number", "bigint"].indexOf(typeof variable) >= 0) {
		return variable.toString(10);
	} else if (Long.isLong(variable)) {
		return variable.toString(10);
	} else if (typeof variable == "function") {
		return null;
	} else if (typeof variable == "object") {
		used.push(variable);
		str = "{" + nl;
		for (k in variable) {
			sval = _iterate_value(variable[k], pad, depth + 1, used);
			if (sval) {
				comma = true;
				str += spad + sp + k + sp + ":" + sval + "," + nl;
			}
		}
		if (comma) {
			str = str.slice(0, 0 - ("," + nl).length) + nl;
			str += pad.repeat(depth - 1) + "}";
		} else {
			str = str.slice(0, -1) + "}";
		}
		return str;
	} else {
		return JSON.stringify(variable);
	}
}

function stringify(variable, pad) {
	pad = pad ? pad : "";
	return _iterate_value(variable, pad, 1, []);
}

export default {
	parse: json_parse(),
	stringify: stringify
};
